"""
Copyright 2008 Olivier Belanger

This file is part of Ounk.

Ounk is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Ounk is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Ounk.  If not, see <http://www.gnu.org/licenses/>.
"""

import wx
from types import ListType, IntType, FloatType
import math

def interpFloat(t, v1, v2):
    "interpolator for a single value; interprets t in [0-1] between v1 and v2"
    return (v2-v1)*t + v1

def tFromValue(value, v1, v2):
    "returns a t (in range 0-1) given a value in the range v1 to v2"
    return float(value-v1)/(v2-v1)

def clamp(v, minv, maxv):
    "clamps a value within a range"
    if v<minv: v=minv
    if v> maxv: v=maxv
    return v

def distance(pos, center):
    if center >= pos:
        return center - pos
    else:
        return pos - center
        
def angle(x, y):
    return math.atan2(y,x)

class SimpleSlider(wx.Slider):
    def __init__(self, parent, id, init, mini, maxi, sliderPos, size, orient, function):
        if orient == wx.VERTICAL:
            wx.Slider.__init__(self, parent, id, init, mini, maxi, sliderPos, size, 
                               orient | wx.SL_AUTOTICKS | wx.SL_LABELS | wx.SL_INVERSE)
        else:
            wx.Slider.__init__(self, parent, id, init, mini, maxi, sliderPos, size, 
                               orient | wx.SL_AUTOTICKS | wx.SL_LABELS)        
        self.function = function
        self.SetTickFreq(5, 1)
        self.SetValue(init)
        self.Bind(wx.EVT_SLIDER, self.call)
        self.call(None)
        
    def call(self, evt):
        self.function(self.GetValue())
        
class CircleSlider(wx.Panel):
    def __init__(self, parent, minvalue=0, maxvalue=1, pos=(0,0), size=(200,200), function=None):
        wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY, pos=pos, size=size)
        self.outFunction = function
        self.SetRange(minvalue, maxvalue)
        self.buttonWidth = 6
        self.borderWidth = 1
        self.size = size
        self.radius = self.size[0] / 2
        self.pos = (size[0]/2,size[1]/2)
        self.SetColors(outline=(255,255,255), bg=(0,0,0), fill=(255,0,0))
        self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown)
        self.Bind(wx.EVT_LEFT_UP, self.MouseUp)
        self.Bind(wx.EVT_MOTION, self.MouseMotion)
        self.Bind(wx.EVT_PAINT, self.OnPaint)

    def SetColors(self, outline, bg, fill):
        self.outlinecolor = wx.Color(*outline)
        self.backgroundcolor = wx.Color(*bg)
        self.fillcolor = wx.Color(*fill)
        
    def SetRange(self, minvalue, maxvalue):   
        self.minvalue = minvalue
        self.maxvalue = maxvalue
            
    def GetValue(self):
        x,y = self.pos
        offset = self.buttonWidth + self.borderWidth
        tx = tFromValue(x, offset, self.size[0]-offset)
        xval = interpFloat(tx, self.minvalue, self.maxvalue)
        ty = tFromValue(y, offset, self.size[0]-offset)
        yval = interpFloat(ty, self.minvalue, self.maxvalue)
        print (clamp(xval, self.minvalue, self.maxvalue),clamp(yval, self.minvalue, self.maxvalue))
        return (clamp(xval, self.minvalue, self.maxvalue),clamp(yval, self.minvalue, self.maxvalue))
        
    def MouseDown(self, evt):
        self.pos = self.clipPos(evt.GetPosition())
        self.CaptureMouse()
        self.Refresh()

    def MouseUp(self, evt):
        self.pos = self.clipPos(evt.GetPosition())
        if self.HasCapture():
            self.ReleaseMouse()
        self.Refresh()

    def MouseMotion(self, evt):
        if evt.Dragging() and evt.LeftIsDown():
            self.pos = self.clipPos(evt.GetPosition())
            self.Refresh()

    def clipPos(self, pos):
        x,y = pos
        disx = x - self.radius
        disy = y - self.radius
        hypo = math.sqrt((disx*disx)+(disy*disy))
        angle = math.atan2(disy, disx)
        limit = self.radius - self.buttonWidth - self.borderWidth
        if hypo >= limit:
            x = limit * math.cos(angle) + self.radius
            y = limit * math.sin(angle) + self.radius
        return (x,y)
       
    def OnPaint(self, evt):
        x,y = (0,0)
        w,h = self.GetSize()
        dc = wx.PaintDC(self)

        dc.SetBrush(wx.Brush(self.backgroundcolor, wx.SOLID))
        dc.Clear()

        dc.SetPen(wx.Pen(self.backgroundcolor, width=self.borderWidth, style=wx.SOLID))
        dc.DrawCircle(w/2, h/2, w/2)

        dc.SetBrush(wx.Brush(self.fillcolor, wx.SOLID))
        dc.SetPen(wx.Pen(self.fillcolor, width=self.borderWidth, style=wx.SOLID))
 
        px, py = self.pos
        dc.DrawCircle(px, py, self.buttonWidth)

        if self.outFunction:
            self.outFunction(self.GetValue())

        evt.Skip()
       
class MultiSlider(wx.Panel):
    def __init__(self, parent, value, minvalue, maxvalue, pos, size=(200,12), sliders=1, 
                 valtype=IntType, orient='horizontal', function=None):
        wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY, pos=pos, size=size)
        self.outFunction = function
        self.orient = orient
        self.myType = valtype
        self.howmany = sliders
        if type(value) != ListType:
            self.value = [value]*self.howmany
        else:
            self.value = value
            if len(value) < self.howmany:
                for i in range(len(value), self.howmany):
                    self.value.append(0)
                    
        self.bin = 0
        self._lastBin = None
        self.SetRange(minvalue, maxvalue)
        for i in range(len(self.value)):
            self.SetValue(i, self.value[i])
        self.borderWidth=1
        self.SetColors(outline=(255,255,255), bg=(0,0,0), fill=(255,0,0))
        self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown)
        self.Bind(wx.EVT_LEFT_UP, self.MouseUp)
        self.Bind(wx.EVT_MOTION, self.MouseMotion)
        self.Bind(wx.EVT_PAINT, self.OnPaint)

    def SetColors(self, outline, bg, fill):
        self.outlinecolor=wx.Color(*outline)
        self.backgroundcolor=wx.Color(*bg)
        self.fillcolor=wx.Color(*fill)
        
    def SetRange(self, minvalue, maxvalue):   
        self.minvalue = minvalue
        self.maxvalue = maxvalue

    def clipBin(self, bin):
        bin = int(bin)
        if bin < 0:
            return 0
        elif bin >= self.howmany:
            return self.howmany - 1
        else:
            return bin
            
    def SetValue(self, bin, value):
        if self.orient == 'horizontal': 
            self._SetValue(bin, value)
        else: 
            if float(self.minvalue) == 0.:
                self._SetValue(bin, self.maxvalue - value)
            else:
                self._SetValue(bin, self.maxvalue - value + self.minvalue)
                
    def _SetValue(self, bin, value):
        self.value[bin] = clamp(value, self.minvalue, self.maxvalue)
            
    def GetValue(self, bin):
        return self.value[bin]

    def GetValues(self):
        if self.orient == 'horizontal':
            if self.myType == IntType:
                values = [int(x) for x in self.value]
            else: 
                values = [x for x in self.value]   
        else:
            realvalues = []
            for i in range(self.howmany):
                if float(self.minvalue) == 0.:
                    realvalues.append(self.maxvalue - self.GetValue(i))
                else:
                    realvalues.append(self.maxvalue - self.GetValue(i) + self.minvalue) 
            if self.myType == IntType:
                values = [int(x) for x in realvalues]
            else: 
                values = [x for x in realvalues] 
        return values
            
    def SetValueFromClick(self, x, bin):
        if self.orient == 'horizontal': 
            size = self.GetSize()[0]
        else: 
            size = self.GetSize()[1]
        t = tFromValue(x, self.borderWidth, size-2*self.borderWidth)
        self._SetValue(bin, interpFloat(t, self.minvalue, self.maxvalue))
        if self._lastBin != None and self._lastBin != bin:
            lastBinValue = self.GetValue(self._lastBin)
            currentValue = self.GetValue(bin)
            if self._lastBin < bin: 
                first = self._lastBin
                second = bin
                inc = (currentValue - lastBinValue) / (second - first)
            else:
                first = bin
                second = self._lastBin
                inc = (lastBinValue - currentValue) / (second - first)
            for i, binup in enumerate(range(first, second)):
                self._SetValue(binup, self.GetValue(first)+(inc*i))

        
    def MouseDown(self, evt):
        if self.orient == 'horizontal': 
            pos = evt.GetPosition()[0]
            self.bin = self.clipBin(evt.GetPosition()[1] / (self.GetSize()[1] / float(self.howmany)))
        else: 
            pos = evt.GetPosition()[1]
            self.bin = self.clipBin(evt.GetPosition()[0] / (self.GetSize()[0] / float(self.howmany)))
        self._lastBin = self.bin
        self.SetValueFromClick(pos, self.bin)
        self.CaptureMouse()
        self.Refresh()

    def MouseUp(self, evt):
        if self.orient == 'horizontal': 
            pos = evt.GetPosition()[0]
            self.bin = self.clipBin(evt.GetPosition()[1] / (self.GetSize()[1] / float(self.howmany)))
        else: 
            pos = evt.GetPosition()[1]
            self.bin = self.clipBin(evt.GetPosition()[0] / (self.GetSize()[0] / float(self.howmany)))
        self.SetValueFromClick(pos, self.bin)
        self._lastBin = None
        if self.HasCapture():
            self.ReleaseMouse()
        self.Refresh()

    def MouseMotion(self, evt):
        if evt.Dragging() and evt.LeftIsDown():
            self._lastBin = self.bin
            if self.orient == 'horizontal': 
                pos = evt.GetPosition()[0]
                self.bin = self.clipBin(evt.GetPosition()[1] / (self.GetSize()[1] / float(self.howmany)))
            else: 
                pos = evt.GetPosition()[1]
                self.bin = self.clipBin(evt.GetPosition()[0] / (self.GetSize()[0] / float(self.howmany)))
            self.SetValueFromClick(pos, self.bin)
            self.Refresh()

    def OnPaint(self, evt):
        x,y = (0,0)
        w,h = self.GetSize()
        dc = wx.PaintDC(self)

        dc.SetBrush(wx.Brush(self.backgroundcolor, wx.SOLID))
        dc.Clear()

        dc.SetPen(wx.Pen(self.outlinecolor, width=self.borderWidth, style=wx.SOLID))
        dc.DrawRectangle(x, y, (w-x), (h-y))

        b = self.borderWidth
        dc.SetBrush(wx.Brush(self.fillcolor, wx.SOLID))
        dc.SetPen(wx.Pen(self.fillcolor, width=b, style=wx.SOLID))
            
        if self.orient == 'horizontal':
            for i in range(self.howmany):
                t = tFromValue(self.GetValue(i), self.minvalue, self.maxvalue)
                pos = b + int(t * (w-2*b))
                heigth = (h-1) / float(self.howmany)
                htop = (h / float(self.howmany)) * i
                htop2 = (h / float(self.howmany)) * (i + 1)
                if math.ceil(heigth + htop) == h or math.ceil(heigth+htop) >= htop2: heigth -= 1
                dc.DrawRoundedRectangle(x+b,htop+b,pos-b,heigth,2)
        else:
            for i in range(self.howmany):
                t = tFromValue(self.GetValue(i), self.minvalue, self.maxvalue)
                pos = b + int(t * (h-2*b))
                width = (w-1) / float(self.howmany)
                wtop = (w / float(self.howmany)) * i
                wtop2 = (w / float(self.howmany)) * (i + 1)
                if math.ceil(width + wtop) == w or math.ceil(width+wtop) >= wtop2: width -= 1
                dc.DrawRoundedRectangle(wtop+b,pos,width,h-b-pos,2)

        if self.outFunction:
            self.outFunction(self.GetValues())

        evt.Skip()

class XYSlider(wx.Panel):
    def __init__(self, parent, minvalue=0, maxvalue=100, pos=(0,0), size=(200,200), function=None):
        wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY, pos=pos, size=size)
        self.outFunction = function
        self.SetRange(minvalue, maxvalue)
        self.buttonWidth = 10
        self.borderWidth = 1
        self.size = size
        self.pos = (size[0]/2,size[1]/2)
        self.SetColors(outline=(255,255,255), bg=(0,0,0), fill=(255,0,0))
        self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown)
        self.Bind(wx.EVT_LEFT_UP, self.MouseUp)
        self.Bind(wx.EVT_MOTION, self.MouseMotion)
        self.Bind(wx.EVT_PAINT, self.OnPaint)

    def SetColors(self, outline, bg, fill):
        self.outlinecolor = wx.Color(*outline)
        self.backgroundcolor = wx.Color(*bg)
        self.fillcolor = wx.Color(*fill)
        
    def SetRange(self, minvalue, maxvalue):   
        self.minvalue = minvalue
        self.maxvalue = maxvalue
            
    def GetValue(self):
        x,y = self.pos
        offset = self.buttonWidth/2 + self.borderWidth
        tx = tFromValue(x, offset, self.size[0]-offset)
        xval = interpFloat(tx, self.minvalue, self.maxvalue)
        ty = tFromValue(y, offset, self.size[1]-offset)
        yval = interpFloat(ty, self.minvalue, self.maxvalue)
        return (xval,yval)
        
    def MouseDown(self, evt):
        self.pos = self.clipPos(evt.GetPosition())
        self.CaptureMouse()
        self.Refresh()

    def MouseUp(self, evt):
        self.pos = self.clipPos(evt.GetPosition())
        if self.HasCapture():
            self.ReleaseMouse()
        self.Refresh()

    def MouseMotion(self, evt):
        if evt.Dragging() and evt.LeftIsDown():
            self.pos = self.clipPos(evt.GetPosition())
            self.Refresh()

    def clipPos(self, pos):
        x,y = pos
        sizex, sizey = self.size
        offset = self.buttonWidth/2 + self.borderWidth        
        if x < offset: x = offset
        elif x > (sizex-offset): x = sizex - offset
        else: x = x 
        if y < offset: y = offset
        elif y > (sizey-offset): y = sizey - offset
        else: y = y
        return (x,y)
        
    def OnPaint(self, evt):
        x,y = (0,0)
        w,h = self.GetSize()
        dc = wx.PaintDC(self)

        dc.SetBrush(wx.Brush(self.backgroundcolor, wx.SOLID))
        dc.Clear()

        dc.SetPen(wx.Pen(self.outlinecolor, width=self.borderWidth, style=wx.SOLID))
        dc.DrawRectangle(x, y, w, h)

        b = self.borderWidth
        dc.SetBrush(wx.Brush(self.fillcolor, wx.SOLID))
        dc.SetPen(wx.Pen(self.fillcolor, width=b, style=wx.SOLID))
 
        px, py = self.pos
        dc.DrawRoundedRectangle(px-(self.buttonWidth/2),py-(self.buttonWidth/2),self.buttonWidth,self.buttonWidth, 2)

        if self.outFunction:
            self.outFunction(self.GetValue())

        evt.Skip()

class RangeSlider(wx.Panel):
    def __init__(self, parent, init, minvalue, maxvalue, pos, size=(200,12), valtype=IntType, function=None):
        wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY, pos=pos, size=size)
        self.outFunction = function
        self.myType = valtype
        totrange = maxvalue - minvalue
        if type(init) != ListType:
            self.values = [0, (float(init) - minvalue) / totrange]
        else:
            self.values = [(float(init[0]) - minvalue) / totrange, (float(init[1]) - minvalue) / totrange]
        self.SetRange(minvalue, maxvalue)
        self.borderWidth=1
        self.SetColors(outline=(255,255,255), bg=(0,0,0), fill=(255,0,0))
        self.Bind(wx.EVT_LEFT_DOWN, self.MouseDown)
        self.Bind(wx.EVT_LEFT_UP, self.MouseUp)
        self.Bind(wx.EVT_MOTION, self.MouseMotion)
        self.Bind(wx.EVT_PAINT, self.OnPaint)

    def SetColors(self, outline, bg, fill):
        self.outlinecolor=wx.Color(*outline)
        self.backgroundcolor=wx.Color(*bg)
        self.fillcolor=wx.Color(*fill)
        
    def SetRange(self, minvalue, maxvalue):   
        self.minvalue = minvalue
        self.maxvalue = maxvalue
            
    def SetValue(self, values):
        self.values[0] = clamp(values[0], self.minvalue, self.maxvalue)
        self.values[1] = clamp(values[1], self.minvalue, self.maxvalue)
                            
    def GetValues(self):
        w,h = self.GetSize()
        totrange = self.maxvalue - self.minvalue
        mini = clamp((self.values[0] - (1./w)) * totrange + self.minvalue, self.minvalue, self.maxvalue)
        maxi = clamp((self.values[1] + (1./w)) * totrange + self.minvalue, self.minvalue, self.maxvalue)
        if self.myType == IntType:
            return [int(mini), int(maxi)]
        else:
            return [mini, maxi] 
        
    def MouseDown(self, evt):
        w,h = self.GetSize()
        self.down = clamp(float(evt.GetPosition()[0]), 1, w-1) / w
        self.values = [self.down, self.down]
        self.CaptureMouse()
        self.Refresh()

    def MouseUp(self, evt):
        if self.HasCapture():
            self.ReleaseMouse()
        #self.Refresh()

    def MouseMotion(self, evt):
        w,h = self.GetSize()
        if evt.Dragging() and evt.LeftIsDown():
            pos = float(clamp(evt.GetPosition()[0], 1, w-1)) / w
            self.values[0] = min(self.down, pos)
            self.values[1] = max(self.down, pos)
            self.Refresh()

    def OnPaint(self, evt):
        x,y = (0,0)
        w,h = self.GetSize()
        dc = wx.PaintDC(self)

        dc.SetBrush(wx.Brush(self.backgroundcolor, wx.SOLID))
        dc.Clear()

        dc.SetPen(wx.Pen(self.backgroundcolor, width=self.borderWidth, style=wx.SOLID))
        dc.DrawRectangle(x, y, (w-x), (h-y))

        b = self.borderWidth
        dc.SetBrush(wx.Brush(self.fillcolor, wx.SOLID))
        dc.SetPen(wx.Pen(self.fillcolor, width=b, style=wx.SOLID))
        
        first = int(self.values[0]*w)
        second = int(self.values[1]*w) - first
        dc.DrawRoundedRectangle(first,b,second,h-(b*2),2)

        if self.outFunction:
            self.outFunction(self.GetValues())

        evt.Skip()
